<template>
  <div class="flow-containers" style="height: 100%" :class="{ 'view-mode': isView }">
    <div style="display: flex;height: 100%;flex-direction: column">
      <div style="border-bottom: 1px solid rgb(218 218 218);height: auto;background-color:#fff;">
        <div style="display: flex; padding: 5px 5px; justify-content: space-between;">
          <a-space>
            <template v-if="leftActionConfig?.fileOpen?.show">
              <a-upload
                  name="file"
                  action=""
                  accept="application/xml"
                  :showUploadList="false"
                  :beforeUpload="openBpmn"
              >
                <a-tooltip effect="dark" title="打开" placement="bottom">
                  <a-button v-if="leftActionConfig?.fileOpen?.show">
                    <i class="workflow icon-file-open"></i>
                  </a-button>
                </a-tooltip>
              </a-upload>
            </template>

            <template v-if="leftActionConfig?.fileAdd?.show">
              <a-tooltip effect="dark" title="新建" placement="bottom">
                <a-button @click="newDiagram"
                >
                  <i class="workflow icon-add"></i>
                </a-button>
              </a-tooltip>
            </template>
            <template v-if="leftActionConfig?.fitViewport?.show">
              <a-tooltip effect="dark" title="自适应屏幕" placement="bottom">
                <a-button @click="fitViewport">
                  <i class="workflow icon-browse"></i>
                </a-button>
              </a-tooltip>
            </template>
            <template v-if="leftActionConfig?.zoomOut?.show">
              <a-tooltip effect="dark" title="放大" placement="bottom">
                <a-button @click="zoomViewport(true)"
                >
                  <i class="workflow icon-zoom-out"></i>
                </a-button>
              </a-tooltip>
            </template>
            <template v-if="leftActionConfig?.zoomIn?.show">
              <a-tooltip effect="dark" title="缩小" placement="bottom">
                <a-button @click="zoomViewport(false)"
                >
                  <i class="workflow icon-zoom-in"></i>
                </a-button>
              </a-tooltip>
            </template>
            <template v-if="leftActionConfig?.undo?.show">
              <a-tooltip effect="dark" title="撤销" placement="bottom">
                <a-button @click="undo()"
                >
                  <i class="workflow icon-undo"></i>
                </a-button>
              </a-tooltip>
            </template>
            <template v-if="leftActionConfig?.redo?.show">
              <a-tooltip title="恢复" placement="bottom">
                <a-button @click="redo()"
                >
                  <i class="workflow icon-redo"></i>
                </a-button>
              </a-tooltip>
            </template>
            <slot name="header-left"></slot>

          </a-space>
          <a-space>
            <slot name="header-right"></slot>
            <a-button @click="showXML" v-if="rightActionConfig.showCode?rightActionConfig.showCode.show:false">
              <i class="workflow icon-browse"
                 v-if="rightActionConfig.showCode.icon"></i>{{ rightActionConfig.showCode.label }}
            </a-button>
            <a-button @click="saveXML(true)"
                      v-if="rightActionConfig.downloadXML?rightActionConfig.downloadXML.show:false">
              <i class="workflow icon-download"
                 v-if="rightActionConfig.downloadXML.icon"></i>{{ rightActionConfig.downloadXML.label }}
            </a-button>
            <a-button @click="saveImg('svg', true)"
                      v-if="rightActionConfig.downloadSVG?rightActionConfig.downloadSVG.show:false">
              <i class="workflow icon-download"
                 v-if="rightActionConfig.downloadSVG.icon"></i>{{ rightActionConfig.downloadSVG.label }}
            </a-button>
            <a-button type="primary" @click="save" v-if="rightActionConfig.save?rightActionConfig.save.show:false">
              <i class="workflow icon-upload" v-if="rightActionConfig.save.icon"></i>{{
                rightActionConfig.save.label
              }}
            </a-button>
          </a-space>
        </div>
      </div>
      <div style="flex: 1;display: flex;flex-direction: row;  position: relative;overflow: hidden" ref="canvasParent">
        <div style="flex: 1;background-color: #f0f2f5" ref="canvas" class="canvas"/>
        <a-drawer rootClassName="workflow-property-drawer" class="workflow-property-drawer"
                  :get-container="()=>{
                     return this.$refs.canvasParent;
                  }" v-model:open="panelVisible" v-model:visible="panelVisible" :style="{ position: 'absolute' }"
                  :mask="false" placement="right" width="400px">
          <template #handle>
            <div
                class="workflow-property-drawer-handle"
                @click="()=>{ panelVisible=!panelVisible}"
            >
              <i v-if="panelVisible" class="workflow icon-right" style="position: absolute"/>

              <i v-else class="workflow icon-left" style="position: absolute"/>
            </div>
          </template>
          <template #title>
            {{ panelTitle }}
          </template>
          <div class="workflow-property-drawer-content">
            <PropertyPanel v-if="modeler"
                           ref="panel"
                           :filters="panelFilters"
                           :modeler="modeler"
                           :users="users"
                           :groups="groups"
                           :categories="categories"
                           :categories-fields="categoriesFields"
                           :associate-form-config="associateFormConfig"
                           :associate-form-data-options="associateFormDataOptions"
                           :assignee-data-source="assigneeDataSource"
                           :due-date-data-source="dueDateDataSource"
                           :follow-up-date-data-source="followUpDateDataSource"
                           :initiator-data-source="initiatorDataSource"
                           :skip-expression-data-source="skipExpressionDataSource"
                           :condition-expression-data-source="conditionExpressionDataSource"
                           :candidate-user-data-source="candidateUserDataSource"
                           :candidate-group-data-source="candidateGroupDataSource"
                           @showForm="showAssociateForm"
                           @createForm="createAssociateForm"
                           @change="handlePanelChange"
            />
          </div>
        </a-drawer>
      </div>
    </div>

    <a-modal v-model:visible="codeVisible" title="XML" width="60%" style="height: 800px">
      <div class="codediv">
        <Codemirror v-model="xmlCode" :options="options"
                    :extensions="extensions"></Codemirror>
      </div>

      <template #footer>
      </template>

    </a-modal>

  </div>

</template>

<script>
// 汉化
import customTranslate from './common/customTranslate'
import Modeler from 'bpmn-js/lib/Modeler'
import PropertyPanel from './PropertyPanel'
import BpmData from './BpmData'
import getInitStr from './flowable/init'
// 引入flowable的节点文件
import flowableModdle from './flowable/flowable.json'

import {Codemirror} from 'vue-codemirror'
import {message} from 'ant-design-vue'
import {xml} from '@codemirror/lang-xml'
import {oneDark} from '@codemirror/theme-one-dark'
import HolderIcon from './icon/HolderIcon.vue'
import {toRaw} from "vue";

const nodePanelFilters = ['子流程', '调用活动']
export default {
  name: 'WorkflowBpmnModeler',
  components: {
    PropertyPanel, Codemirror, HolderIcon
  },
  props: {
    categoriesFields: {
      type: Object
    },
    //过滤面板参数
    panelFilters: {
      type: Array
    },
    paletteToolShow: {
      type: Boolean,
      default: () => true
    },
    //控制过滤
    paletteFilters: {
      type: Array,
      default: () => []
    },
    leftActionConfig: {
      type: Object,
      default: () => {
        return {
          "fileOpen": {
            "show": true,
            action: () => {
            }
          },
          "fileAdd": {
            "show": true,
          },
          "fitViewport": {
            "show": true,
          },
          "zoomIn": {
            "show": true,
          },
          "zoomOut": {
            "show": true,
          },
          "undo": {
            "show": true,
          },
          "redo": {
            "show": true,
          },
        }
      }
    },
    rightActionConfig: {
      type: Object,
      default: () => {
        return {
          "showCode": {
            "show": true,
            "icon": true,
            "label": "XML"
          },
          "downloadXML": {
            "show": true,
            "icon": true,
            "label": "XML"
          },
          "downloadSVG": {
            "show": true,
            "icon": true,
            "label": "SVG"
          },
          "save": {
            "show": true,
            "icon": true,
            "label": "保存"
          },
        }
      }
    },
    bpmnXml: {
      type: String,
      default: ''
    },
    users: {
      type: Array,
      default: () => []
    },
    groups: {
      type: Array,
      default: () => []
    },
    categories: {
      type: Array,
      default: () => []
    },
    associateFormConfig: {
      type: Object
    },
    associateFormDataOptions: {
      type: Array,
      default: () => []
    },
    assigneeDataSource: {
      type: Array,
      default: () => ["#{approval}", "${approverId}", "${INITIATOR}"]
    },
    dueDateDataSource: {
      type: Array,
      default: () => ["${dueDate}"]
    },
    followUpDateDataSource: {
      type: Array,
      default: () => ["${followUpDate}"]
    },
    initiatorDataSource: {
      type: Array,
      default: () => ["initiator"]
    },
    skipExpressionDataSource: {
      type: Array,
      default: () => []
    },
    conditionExpressionDataSource: {
      type: Array,
      default: () => ['${approve}', '${!approve}']
    },
    candidateUserDataSource: {
      type: Array,
      default: () => []
    },
    candidateGroupDataSource: {
      type: Array,
      default: () => []
    },
  },
  data() {
    return {
      panelTitle: '',
      panelVisible: false,
      modeler: null,
      taskList: [],
      zoom: 1,
      codeVisible: false,
      xmlCode: '',
      //需编辑和显示的内容
      // 默认配置
      extensions: [xml(), oneDark],
      options: {
        tabSize: 2, // 缩进格式
        theme: 'rubyblue', // 指定主题，对应主题库 JS 需要提前引入
        lineNumbers: true, // 是否显示行号
        //指定语言类型,如果需要编辑和显示其他语言,需要import语言js然后修改此配置
        mode: 'xml',
        line: true,
        styleActiveLine: true, // 高亮选中行
        //是否为只读,如果为"nocursor" 不仅仅为只读 连光标都无法在区域聚焦
        readOnly: true,
        hintOptions: {
          completeSingle: true // 当匹配只有一项的时候是否自动补全
        }
      }
    }
  },
  watch: {
    bpmnXml(val) {
      // 在组件实例创建时会立即调用
      if (val) {
        this.createNewDiagram(val)
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      // 生成实例
      this.modeler = new Modeler({
        container: this.$refs.canvas,
        additionalModules: [
          {
            translate: ['value', customTranslate]
          }
        ],
        moddleExtensions: {
          flowable: flowableModdle
        }
      })
      // 新增流程定义
      if (!this.bpmnXml) {
        this.newDiagram()
      } else {
        this.createNewDiagram(this.bpmnXml)
      }
    })

  },
  methods: {
    handlePanelChange(title) {
      if (typeof title !== 'string') {
        return
      }
      if (title.trim() === '') {
        this.panelVisible = false
      } else {
        if (nodePanelFilters.indexOf(title.trim()) === -1) {
          this.panelVisible = true
        } else {
          this.panelVisible = false
        }

      }
      this.panelTitle = title

    },
    newDiagram() {
      this.createNewDiagram(getInitStr())
    },
    // 让图能自适应屏幕
    fitViewport() {
      this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
      const bbox = document.querySelector('.flow-containers .viewport').getBBox()
      const currentViewbox = this.modeler.get('canvas').viewbox()
      const elementMid = {
        x: bbox.x + bbox.width / 2 - 65,
        y: bbox.y + bbox.height / 2
      }
      this.modeler.get('canvas').viewbox({
        x: elementMid.x - currentViewbox.width / 2,
        y: elementMid.y - currentViewbox.height / 2,
        width: currentViewbox.width,
        height: currentViewbox.height
      })
      this.zoom = bbox.width / currentViewbox.width * 1.8
    },
    // 放大缩小
    zoomViewport(zoomIn = true) {
      this.zoom = this.modeler.get('canvas').zoom()
      this.zoom += (zoomIn ? 0.1 : -0.1)
      this.modeler.get('canvas').zoom(this.zoom)
    },
    redo() {
      toRaw(this.modeler).get('commandStack').redo()
    },
    undo() {
      toRaw(this.modeler).get('commandStack').undo()
    },
    async createNewDiagram(data) {
      // 将字符串转换成图显示出来
      // data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
      data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function (match, str) {
        return str.replace(/</g, '&lt;')
      })
      try {
        await this.modeler.importXML(data)
        this.adjustPalette()
        this.fitViewport()
        // this.fillColor()
      } catch (err) {
        console.error(err.message, err.warnings)
      }
    },
    // 调整左侧工具栏排版
    adjustPalette() {
      try {
        // 获取 bpmn 设计器实例
        const canvas = this.$refs.canvas
        const djsPalette = canvas.children[0].children[1].children[4]
        if (!djsPalette) {
          return
        }
        const djsPalStyle = {
          width: '130px',
          padding: '5px',
          background: 'white',
          left: '20px',
          borderRadius: 0
        }
        for (var key in djsPalStyle) {
          djsPalette.style[key] = djsPalStyle[key]
        }
        const palette = djsPalette.children[0]
        const allGroups = palette.children
        //是否隐藏工具栏
        if (!this.paletteToolShow) {
          allGroups[0].style['display'] = 'none'
        }

        // 修改控件样式
        for (var gKey in allGroups) {
          const group = allGroups[gKey]
          for (var cKey in group.children) {
            const control = group.children[cKey]
            const controlStyle = {
              display: 'flex',
              justifyContent: 'flex-start',
              alignItems: 'center',
              width: '100%',
              padding: '5px'
            }
            if (
                control.className &&
                control.dataset &&
                control.className.indexOf('entry') !== -1
            ) {
              const controlProps = new BpmData().getControl(
                  control.dataset.action
              )
              control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;'>${
                  controlProps['title']
              }</div>`
              for (var csKey in controlStyle) {
                control.style[csKey] = controlStyle[csKey]
              }

              //过滤扩展栏
              if (this.paletteFilters.indexOf(control.dataset.action) !== -1) {
                control.style['display'] = 'none'
                continue
              }
            }
          }
        }
      } catch (e) {
        console.log(e)
      }
    },
    fillColor() {
      const canvas = this.modeler.get('canvas')
      this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
        if (n.$type === 'bpmn:UserTask') {
          const completeTask = this.taskList.find(m => m.key === n.id) || {completed: true}
          const todoTask = this.taskList.find(m => !m.completed)
          const endTask = this.taskList[this.taskList.length - 1]
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {
                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
              } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
                // canvas.addMarker(nn.id, 'highlight');
                canvas.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo')
                canvas.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo')
              } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
                if (!todoTask && endTask.key === n.id) {
                  canvas.addMarker(nn.id, 'highlight')
                  canvas.addMarker(nn.targetRef.id, 'highlight')
                }
                if (!completeTask.completed) {
                  canvas.addMarker(nn.id, 'highlight-todo')
                  canvas.addMarker(nn.targetRef.id, 'highlight-todo')
                }
              }
            })
          }
        } else if (n.$type === 'bpmn:ExclusiveGateway') {
          n.outgoing.forEach(nn => {
            const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
            if (targetTask) {
              canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
            }
          })
        }
        if (n.$type === 'bpmn:StartEvent') {
          n.outgoing.forEach(nn => {
            const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
            if (completeTask) {
              canvas.addMarker(nn.id, 'highlight')
              canvas.addMarker(n.id, 'highlight')
              return
            }
          })
        }
      })
    },
    // 对外 api
    getProcess() {
      const element = this.getProcessElement()
      return {
        id: element.id,
        name: element.name,
        category: element.$attrs['flowable:processCategory']
      }
    },
    getProcessElement() {
      const rootElements = this.modeler.getDefinitions().rootElements
      for (let i = 0; i < rootElements.length; i++) {
        if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
      }
    },
    async showXML() {
      this.xmlCode = await this.saveXML(false)
      this.codeVisible = true;
    },
    async saveXML(download = false) {
      try {
        const {xml} = await this.modeler.saveXML({format: true})
        if (download) {
          this.downloadFile(`${this.getProcessElement().name}.bpmn20.xml`, xml, 'application/xml')
        }
        return xml
      } catch (err) {
        console.log(err)
      }
    },
    async saveImg(type = 'svg', download = false) {
      try {
        const {svg} = await this.modeler.saveSVG({format: true})
        if (download) {
          this.downloadFile(this.getProcessElement().name, svg, 'image/svg+xml')
        }
        return svg
      } catch (err) {
        console.log(err)
      }
    },
    async save() {
      const process = this.getProcess()
      const xml = await this.saveXML()
      const svg = await this.saveImg()
      const result = {process, xml, svg}
      this.$emit('save', result)
      window.parent.postMessage(result, '*')
    },
    openBpmn(file) {
      console.log(file)
      if (file.type !== 'text/xml') {
        message.error("需要打开bpmn20.xml文件")
        return
      }
      const reader = new FileReader()
      reader.readAsText(file, 'utf-8')
      reader.onload = () => {
        this.createNewDiagram(reader.result)
      }
      return false
    },
    downloadFile(filename, data, type) {
      var a = document.createElement('a')
      var url = window.URL.createObjectURL(new Blob([data], {type: type}))
      a.href = url
      a.download = filename
      a.click()
      window.URL.revokeObjectURL(url)
    },
    showAssociateForm(formKey) {
      this.$emit("showForm", formKey)
    },
    createAssociateForm() {
      this.$emit("createForm")
    }
  }
}
</script>

<style lang="less">
// Font class
@import "./icon/iconfont.css";
/*左边工具栏以及编辑节点的样式*/
@import "bpmn-js/dist/assets/diagram-js.css";
@import "bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
@import "bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
@import "bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";

.workflow-property-drawer {
  .ant-drawer-content-wrapper {
    width: 400px !important;
  }
}
</style>
<style lang="less" scoped>
@import "./PropertyDrawer.less";

.codediv {
  height: 600px;

  :deep(.cm-editor) {
    height: 100%;
  }
}

.iconfont {
  margin-right: 5px;
}

.flow-containers {
  // background-color: #ffffff;
  width: 100%;
  height: 100%;

  .canvas {
    width: 100%;
    height: 100%;
  }

  .panel {
    position: absolute;
    right: 0;
    top: 50px;
    //width: 400px;
  }

  .load {
    margin-right: 10px;
  }

  .djs-palette {
    left: 0px !important;
    top: 0px;
    border-top: none;
  }

  .djs-container svg {
    min-height: 650px;
  }

}

:deep(.ant-layout-sider:hover) {
  background: #0A97CE25 !important;
}

:deep(.ant-layout-sider) {
  z-index: 2;
}

:deep(.CodeMirror) {
  border: 1px solid #eee;
  height: 900px !important;
}

:deep(.djs-container) {
  background: url() repeat !important;
}
</style>
